// Graphic Classes.
// ================

// The sprite class.
public class pp_sprite {
  int pixFrames, pixWidth, waitFrames, currentFrame, currentWait;
  PImage sprite;
  
  pp_sprite (int frames, int fps, boolean flipX, boolean flipY, String colormap, String bumpmap) {
    
    // Test if file exists.
    if (pp_fileExists(colormap)) {
      
      // Load the sprite.
      sprite = loadImage(colormap);
      pixFrames = constrain(frames, 1, 999) - 1;
      
      // Process flipping.
      if (flipX) {sprite = pp_flipX(sprite, frames);}
      
      // Check if animated.
      // Single frame.
      if (pixFrames == 0) {pixWidth = sprite.width;}
      
      // Animated.
      else {
        
        // Test if dimensions are correct. (Width/frames should result in int)
        if (sprite.width % frames == 0) {pixWidth = sprite.width / frames;}
        else {println("Image " + dataPath(colormap)+ "has incorrect width for amount of frames.");}
        
        // Determine wait time before each frame.
        waitFrames = constrain(round(pp_frameRate / fps), 1, 100) - 1;
      }
    } else {
      println("Image " + dataPath(colormap) + " not found.");
    }
  }
}

// Graphic Functions.
// ==================

// Draw a pixel onto pixelMatrix array.
void pp_drawPixel (int x, int y, color rgb) {
  
  // Test for alpha value of pixel.
  switch ((rgb >> 24) & 0xFF){
    
    // If pixel is completely transparent, ignore.
    case 0:
      break;
  
    // If pixel is opaque, replace original pixel.
    case 255:
      pp_pixelMatrix[(x - pp_displayX) + ((y - pp_displayY) * pp_matrixWidth)] = rgb;
      break;    
    
    // If pixel has alpha value, modify original pixel instead.
    default:
      int i = (x - pp_displayX) + ((y - pp_displayY) * pp_matrixWidth);
      pp_pixelMatrix[i] = 
        ((pp_alphaCalc((rgb >> 16) & 0xFF, (pp_pixelMatrix[i] >> 16) & 0xFF, (rgb >> 24) & 0xFF)) << 16) |
        ((pp_alphaCalc((rgb >> 8) & 0xFF, (pp_pixelMatrix[i] >> 8) & 0xFF, (rgb >> 24) & 0xFF)) << 8) |
        (pp_alphaCalc(rgb & 0xFF, pp_pixelMatrix[i] & 0xFF, (rgb >> 24) & 0xFF));
      break;
  }
}

// Draw sprite onto pixelMatrix array.
void pp_drawSpriteToMatrix (int x, int y, int frame, int alpha, int testResult, String spriteName) {
  
  // Get sprite.
  pp_sprite sprite = spr.get(spriteName);
  
  // Fail safe in case sprite is missing.
  if (sprite == null) {testResult = 0;}
  
  switch (testResult) {    
    // If partially on screen...
    case 1:    
      // Get margins.
      int startX = (x < pp_displayX) ? (pp_displayX - x) : 0;
      int endX = (x + sprite.pixWidth >= int(width/pp_pixelSize) + pp_displayX) ? ((int(width/pp_pixelSize) + pp_displayX) - x) : sprite.pixWidth;
      int startY = (y < pp_displayY) ? (pp_displayY - y) : 0;
      int endY = (y + sprite.sprite.height >= int(height/pp_pixelSize) + pp_displayY) ? ((int(height/pp_pixelSize) + pp_displayY) - y) : sprite.sprite.height;
      
      // Render partially to screen.
      for (int i = startX; i < endX; i++) {
        for (int k = startY; k < endY; k++) {
          color rgb = sprite.sprite.pixels[i + (sprite.pixWidth * frame) + (k * sprite.sprite.width)];
          if (alpha < 255) { rgb = pp_applyAlpha(alpha, rgb); }           
          pp_drawPixel(i + x, k + y, rgb);
        }
      }
      break;
    
    // If entirely on screen, render all.
    case 2:
      for (int i = 0; i < sprite.pixWidth; i++) {
        for (int k = 0; k < sprite.sprite.height; k++) {
          color rgb = sprite.sprite.pixels[i + (sprite.pixWidth * frame) + (k * sprite.sprite.width)];
          if (alpha < 255) { rgb = pp_applyAlpha(alpha, rgb); }           
          pp_drawPixel(i + x, k + y, rgb);
        }
      }
      break;
  }
}

// Add sprite to depthBuffer.
void pp_drawSprite (int x, int y, int depth, int frame, String spriteName) {pp_drawSprite (x, y, depth, frame, 255, spriteName); }
void pp_drawSprite (int x, int y, int depth, int frame, int alpha, String spriteName) {
  
  // Get sprite.
  pp_sprite sprite = spr.get(spriteName);
  
  // Test if all edges are on screen.
  int testResult = pp_toInt(pp_testOnScreen(x, y)) + pp_toInt(pp_testOnScreen(x + sprite.pixWidth, y + sprite.sprite.height));
  
  // If partially or completely on-screen, render it.
  if (testResult > 0) {
    if (depth == 0) {
      pp_drawSpriteToMatrix (x, y, frame, alpha, testResult, spriteName);
    
    // Else, add to depthBuffer.
    } else {
      depthBuffer.append(nf(depth,4) + "0" + str(pp_signToInt(x)) + nf(abs(x),4) + str(pp_signToInt(y)) + nf(abs(y),4) + nf(frame,4) + nf(alpha,3) + str(testResult) + spriteName);
    }
  }
}

// Apply alpha to color.
color pp_applyAlpha (int alpha, color rgb) {
  rgb = 
    ((((rgb >> 24) & 0xFF) * alpha / 255) << 24) |
    rgb & 0xFFFFFF;
  return rgb;
}

// Get sprite width.
int pp_getWidth(String spriteName) {return spr.get(spriteName).pixWidth;}

// Get sprite height.
int pp_getHeight(String spriteName) {return spr.get(spriteName).sprite.height;}

// Test if pixel is onscreen.
boolean pp_testOnScreen (int x, int y) {
  if ((x >= pp_displayX) && (x < (width / pp_pixelSize) + pp_displayX) && (y >= pp_displayY) && (y < (height / pp_pixelSize) + pp_displayY)) {
    return true;
  } else {
    return false;
  }
}

// Test if pixel is on level.
boolean pp_testOnLevel (int x, int y) {
  if ((x >= 0) && (x < pp_roomWidth) && (y >= 0) && (y < pp_roomHeight)) {
    return true;
  } else {
    return false;
  }
}

// Alpha calculator (single channel)
int pp_alphaCalc (float color1, float color2, float alpha) {
  return int((alpha/255) * color1 + (1-(alpha/255)) * color2);
}

// Correct pixelSize to nearest common denominator of screen width and height.
void pp_setPixelSize(int pixelSize) {
  int correction = 0;
  while (true){
    if ((width % (pixelSize + correction) == 0) && (height % (pixelSize + correction) == 0)) {
      pp_pixelSize += correction;
      break;
    } else {
      if ((width % (pixelSize - correction) == 0) && (height % (pixelSize - correction) == 0)) {
        pp_pixelSize += correction;
        break;
      }
    }
    correction += 1;
  }
}

// Flip X.
PImage pp_flipX (PImage img, int frames) {
  PImage reverse = createImage(img.width, img.height, ARGB);
  int frameWidth = int(img.width/frames);
  for (int i = 0; i < frames; i++) {
    for (int x = 0; x < frameWidth; x++) {
      for (int y = 0; y < img.height; y++) {
        reverse.pixels[(frameWidth * i) + x + (y * img.width)] = img.pixels[(frameWidth * i) + (frameWidth - x - 1) + (y * img.width)];
      }
    }
  }
  return reverse;
}
